#include <errno.h>
#include <stdio.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <net/if.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <pthread.h>
#include <string.h>
#include "caevent.h"
#include "casock.h"
#include "calog.h"

#define CALL_SOCKCB(psock, EVENT) (psock->sockcb)? (psock->sockcb(psock, EVENT)) : (psock->psys->msg_proc(psock->psys, EM_SOCKET, EVENT, psock->udata)); 
		

static void sock_event_cb(evt_t* pevt, int fd, int events);


static void sock_event_cb(evt_t* pevt, int fd, int events)
{
	sock_t* psock = (sock_t*)pevt->user;
	int sockerr;
	socklen_t socklen = sizeof(sockerr);
	
	if(psock->status == SOCK_STATUS_CONNECTING)
	{
		getsockopt(psock->fd, SOL_SOCKET, SO_ERROR, &sockerr, &socklen);
		logprt("    sock error=%d\n", sockerr);					
		if(sockerr!=0)
		{
			if(sockerr==ECONNREFUSED)
				logprt("### connection refused.....err=%d\n", sockerr);
			else if(sockerr==ETIMEDOUT)
				logprt("### connection timeout......err=%d\n", sockerr);
			else if(sockerr==EHOSTUNREACH)
				logprt("### connection host_unreach...err=%d\n", sockerr);
			else
				logprt("### connection etc error...err=%d\n", sockerr);
			//if(psock->sockcb)
			//	psock->sockcb(psock, SOCK_EVENT_DISCONNECTED);
			CALL_SOCKCB(psock, SOCK_EVENT_DISCONNECTED);
			es_tcp_close(psock);
		}
		else
		{
			//logprt("=== connected, fd=%d, \n", psock->fd);
			psock->status = SOCK_STATUS_CONNECTED; // TODO: must check conneced or refused connecting....
			es_change(pevt, EPOLLIN);						
			//if(psock->sockcb)
			//	psock->sockcb(psock, SOCK_EVENT_CONNECTED);
			CALL_SOCKCB(psock, SOCK_EVENT_CONNECTED);

						
		}
	}	
	else if(psock->status == SOCK_STATUS_CONNECTED)
	{
		int ret;
		//ret = psock->sockcb(psock, SOCK_EVENT_READ);

		if(events & EPOLLOUT) {
			logprt("XXX : OUT event.....\n");
			CALL_SOCKCB(psock, SOCK_EVENT_WRITE);		
		}
		
		if(events & EPOLLIN)
			ret = CALL_SOCKCB(psock, SOCK_EVENT_READ);
		if(ret == 0 && psock->fd != 0) {
			es_tcp_close(psock);
			//if(psock->sockcb)
			//	psock->sockcb(psock, SOCK_EVENT_DISCONNECTED);
			CALL_SOCKCB(psock, SOCK_EVENT_DISCONNECTED);
	}
			
	}
	else if(psock->status == SOCK_STATUS_LISTENING)
	{
		//psock->sockcb(psock, SOCK_EVENT_INCOMING_ACCEPT);
		CALL_SOCKCB(psock, SOCK_EVENT_INCOMING_ACCEPT);

	}
}

static void udp_sock_event_cb(evt_t* pevt, int fd, int events)
{
	sock_t* psock = (sock_t*)pevt->user;
	if(events & EPOLLOUT) 
	{
		CALL_SOCKCB(psock, SOCK_EVENT_WRITE);
	}

	if(events & EPOLLIN)
	{
		CALL_SOCKCB(psock, SOCK_EVENT_READ);
	}
}



int es_sock_open(es_t* psys, sock_t* psock, int stype, SOCKCB cb, void *user)
{

	memset(psock, 0, sizeof(sock_t));
	psock->psys = psys;
	psock->sockcb = cb;
	psock->sock_type = stype;
	if(stype == KSOCK_TYPE_TCP)
		psock->fd = socket(AF_INET, SOCK_STREAM, 0);
	else if(stype == KSOCK_TYPE_UDP)
		psock->fd = socket(AF_INET, SOCK_DGRAM, 0);
	psock->user = user;
	
	//logprt("open tcp sock=%d\n", psock->fd);
	if(psock->fd>0)
	{
		setNonBlockMode(psock->fd);
		return 0;
	}	
	return -1;
}


void es_sock_close(sock_t* psock)
{
	//logprt("close tcp sock = %d\n", psock->fd);
	if(psock->fd==0)
		return;
	if(psock->pevt)
		es_dereg(psock->psys, psock->pevt);
	psock->pevt = NULL;	
	close(psock->fd);
	psock->fd = 0;
	psock->status = SOCK_STATUS_DISCONNECTED;

}


int es_tcp_open(es_t* psys, sock_t* psock, SOCKCB cb, void *user)
{

	memset(psock, 0, sizeof(sock_t));
	psock->psys = psys;
	psock->sockcb = cb;
	psock->fd = socket(AF_INET, SOCK_STREAM, 0);
	psock->user = user;
	psock->sock_type = KSOCK_TYPE_TCP;
	
	//logprt("open tcp sock=%d\n", psock->fd);
	if(psock->fd>0)
	{
		setNonBlockMode(psock->fd);
		return 0;
	}	
	return -1;
}

int es_connect(sock_t* psock, uint32_t ip, int port)
{
	struct sockaddr_in sckaddr; 
	
	sckaddr.sin_family = AF_INET;
	sckaddr.sin_port = htons(port);
	sckaddr.sin_addr.s_addr = ip;
	
	int cnnret = connect(psock->fd, (struct sockaddr*)&sckaddr, sizeof(sckaddr));
	//logprt("	connecting to %s ......., ret=%d, errno=%d\n", addr, cnnret, errno);
	if(psock->sock_type == KSOCK_TYPE_TCP)
	{
		psock->pevt = es_reg(psock->psys, psock->fd, EPOLLOUT,  sock_event_cb, (void*)psock);
		psock->status = SOCK_STATUS_CONNECTING;
	}
	else if(psock->sock_type == KSOCK_TYPE_UDP)
	{
		psock->status = SOCK_STATUS_CONNECTED;
		if(psock->pevt == NULL)
			psock->pevt = es_reg(psock->psys, psock->fd, EPOLLIN, udp_sock_event_cb, (void*)psock);
	}
	else
	{
		cnnret = -1;
	}
	return cnnret;
}

int es_connect(sock_t* psock, char* ipaddr, int port)
{
	uint32_t ip = inet_addr(ipaddr);
	return es_connect(psock, ip, port);
}


int es_sock_listen(sock_t* psock, char* ipaddr, int port)
{
	int ret;
	struct sockaddr_in inaddr;
	inaddr.sin_family = AF_INET;
	inaddr.sin_port = htons(port);
	inaddr.sin_addr.s_addr = inet_addr(ipaddr);
	ret = bind(psock->fd, (struct sockaddr*)&inaddr, sizeof(inaddr));
	if(ret)
		return ret;
	if(psock->sock_type == KSOCK_TYPE_TCP)
	{
		ret = listen(psock->fd, 10);
		if(ret)
			return ret;
	}		
	psock->status = SOCK_STATUS_LISTENING;
	if(psock->sock_type == KSOCK_TYPE_TCP)
		psock->pevt = es_reg(psock->psys, psock->fd, EPOLLIN, sock_event_cb, (void*)psock);
	else if(psock->sock_type == KSOCK_TYPE_UDP)
		psock->pevt = es_reg(psock->psys, psock->fd, EPOLLIN, udp_sock_event_cb, (void*)psock);
	else 
	{
		ret = -1;
	}	
	return ret;

}


int es_tcp_listen(sock_t* psock, char* ipaddr, int port)
{
	int ret;
	struct sockaddr_in inaddr;
	inaddr.sin_family = AF_INET;
	inaddr.sin_port = htons(port);
	inaddr.sin_addr.s_addr = inet_addr(ipaddr);
	ret = bind(psock->fd, (struct sockaddr*)&inaddr, sizeof(inaddr));
	if(ret)
		return ret;
	ret = listen(psock->fd, 10);
	if(ret)
		return ret;
		
	psock->status = SOCK_STATUS_LISTENING;
	psock->pevt = es_reg(psock->psys, psock->fd, EPOLLIN, sock_event_cb, (void*)psock);
	
	return ret;
}


int es_tcp_accept(sock_t* plisten, sock_t* psock, SOCKCB cb, void* user)
{
	struct sockaddr_in inaddr;
	int ret;
	int fd;
	socklen_t slen;
	memset(psock, 0, sizeof(sock_t));
	
	fd = accept(plisten->fd, (struct sockaddr*)&inaddr, &slen);
	if(fd>0) {
		psock->fd = fd;
		psock->psys = plisten->psys;
		psock->status = SOCK_STATUS_CONNECTED;
		psock->remote_ip = inaddr.sin_addr.s_addr;
		psock->remote_port = inaddr.sin_port;
		psock->sockcb = cb;
		psock->user = user;
		setNonBlockMode(fd);
		psock->pevt = es_reg(plisten->psys,  fd,  EPOLLIN, sock_event_cb, (void*)psock);
	}
	return fd;
	
}


void es_tcp_reject(sock_t* plisten)
{
	sock_t sock;
	int fd;
	socklen_t slen;
	struct sockaddr_in inaddr;
	
	fd = accept(plisten->fd, (struct sockaddr*)&inaddr, &slen);
	close(fd);
}


void es_tcp_close(sock_t* psock)
{
#if 0
	//logprt("close tcp sock = %d\n", psock->fd);
	if(psock->fd==0)
		return;
	if(psock->pevt)
		es_dereg(psock->psys, psock->pevt);
	psock->pevt = NULL;	
	close(psock->fd);
	psock->fd = 0;
	psock->status = SOCK_STATUS_DISCONNECTED;
#endif
	es_sock_close(psock);
}


int es_read(sock_t* psock, void* buf, size_t size)
{	int ret;
	ret = read(psock->fd, buf, size);
	return ret;	
}

int es_write(sock_t* psock, void* buf, size_t size)
{
	return write(psock->fd, buf, size);
}


int es_set_sock_event(sock_t* psock, int events)
{
	if(psock->fd == 0)
		return -1;
	return es_change(psock->pevt, events);
}
